package pres.lnk.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * <p>RestfulApi请求工具</p>
 * <a href="http://blog.csdn.net/lnktoking/article/details/79174187">使用说明</a>
 *
 * <pre> 更新记录
 * 2019-4-16 新增异步请求
 * 2020-2-25 支持上传数据流
 * 2020-3-7 新增 jsonBody 方法，指定发送json数据；修复请求失败获取错误内容失败
 * 2020-7-26 新增支持文件下载
 * 2022-5-11 增加https证书认证支持，相关支持类代码：https://gitee.com/lnkToKing/open-share-code/tree/master/src/utils/http
 * </pre>
 *
 * @author lnk
 */
public class RestfulHttpClient {
    /** 全局默认请求头 */
    private static final Map<String, String> defaultHeaders = new HashMap<>();
    /** 普通文本类型媒体，支持正则表达式 */
    private static final Set<String> textContentTypes = new HashSet<String>() {{
        add("text/.*");
        add("application/json");
    }};

    /** 全局连接初始化器 */
    private static List<URLConnectionInitializer> initializers;

    private static ExecutorService poolExecutor;
    private static final String charset = "UTF-8";
    private static final int readTimeout = 60000;
    private static final int connectTimeout = 60000;

    public static final String METHOD_GET = "GET";
    public static final String METHOD_POST = "POST";
    public static final String METHOD_PUT = "PUT";
    public static final String METHOD_PATCH = "PATCH";
    public static final String METHOD_DELETE = "DELETE";

    private RestfulHttpClient() {
    }

    /**
     * 发起请求
     *
     * @param method 请求方式：GET、POST、PUT、PATCH、DELETE
     * @param url    请求url
     * @param body   请求体body
     * @throws IOException
     */
    public static HttpResponse request(String method, String url, Object body) throws IOException {
        return request(method, url, body, defaultHeaders);
    }

    /**
     * 发起请求
     *
     * @param method  请求方式：GET、POST、PUT、PATCH、DELETE
     * @param url     请求url
     * @param body    请求体body
     * @param headers 请求头
     * @throws IOException
     */
    public static HttpResponse request(String method, String url, Object body, Map<String, String> headers) throws IOException {
        return getClient(url).method(method).body(body).headers(headers).request();
    }

    /**
     * 获取请求客户端
     */
    public static HttpClient getClient(String url) {
        return new HttpClient(url);
    }

    /**
     * 添加全局请求连接初始化器
     */
    public static void addInitializer(URLConnectionInitializer initializer) {
        if (initializer == null) {
            throw new NullPointerException("不能添加空的连接初始化器");
        }
        if (initializers == null) {
            initializers = new ArrayList<>();
        }
        initializers.add(initializer);
    }

    /**
     * 请求客户端
     */
    public static class HttpClient {
        private Map<String, String> headers;
        private int readTimeout = RestfulHttpClient.readTimeout;
        private int connectTimeout = RestfulHttpClient.connectTimeout;
        /** 设置本次请求类型：0.普通请求，1.文件下载，2.自动识别 */
        private int downloadFileType = 0;
        private String method = METHOD_GET;
        private String url;
        private Map<String, String> pathParams;
        private Map<String, String> queryParams;
        private Map<String, String> postParams;
        private Object body;
        private List<UploadStream> uploadStreams;
        private HttpResponse response;
        private List<URLConnectionInitializer> initializers;

        public HttpClient(String url) {
            if (StringUtils.isBlank(url)) {
                throw new IllegalArgumentException("请求地址不能为空");
            }
            this.url = url;
            headers = new HashMap<>();
            pathParams = new HashMap<>();
            queryParams = new LinkedHashMap<>();
            postParams = new LinkedHashMap<>();
            headers.putAll(defaultHeaders);
            initializers = RestfulHttpClient.initializers;
        }

        public HttpClient get() {
            method = METHOD_GET;
            return this;
        }

        public HttpClient post() {
            method = METHOD_POST;
            return this;
        }

        public HttpClient put() {
            method = METHOD_PUT;
            return this;
        }

        public HttpClient patch() {
            method = METHOD_PATCH;
            return this;
        }

        public HttpClient delete() {
            method = METHOD_DELETE;
            return this;
        }

        /**
         * 添加请求头
         */
        public HttpClient addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        /**
         * 批量添加请求头
         */
        public HttpClient addHeaders(Map<String, String> headers) {
            this.headers.putAll(headers);
            return this;
        }

        /**
         * 设置读取超时时间
         */
        public HttpClient readTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        /**
         * 设置连接超时时间
         */
        public HttpClient connectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
            return this;
        }

        /**
         * 重置请求头
         */
        public HttpClient headers(Map<String, String> headers) {
            this.headers.clear();
            this.headers.putAll(defaultHeaders);
            return addHeaders(headers);
        }

        /**
         * 设置请求方式，默认：GET
         */
        public HttpClient method(String method) {
            if (StringUtils.isBlank(method)) {
                throw new IllegalArgumentException("请求方式不能为空");
            }
            //请求方式不做限制
//            if(!ArrayUtils.contains(new String[]{METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE}, method.toUpperCase())){
//                throw new IllegalArgumentException("请求方式设置错误，不能设置为：" + method);
//            }
            this.method = method.toUpperCase();
            return this;
        }

        /**
         * 设置请求地址，可带path参数
         * 如：/user/{id}
         */
        public HttpClient url(String url) {
            this.url = url;
            return this;
        }

        /**
         * 设置请求地址参数，替换url上的参数
         * 如：/user/{id} 上的{id}
         */
        public HttpClient pathParams(Map<String, String> pathParams) {
            this.pathParams.putAll(pathParams);
            return this;
        }

        /**
         * 添加请求地址参数，替换url上的参数
         * 如：/user/{id} 上的{id}
         */
        public HttpClient addPathParam(String key, String value) {
            this.pathParams.put(key, value);
            return this;
        }

        /**
         * 设置url请求参数，url问号后面的参数
         */
        public HttpClient queryParams(Map<String, String> queryParams) {
            this.queryParams.putAll(queryParams);
            return this;
        }

        /**
         * 添加url请求参数，url问号后面的参数
         */
        public HttpClient addQueryParam(String key, String value) {
            this.queryParams.put(key, value);
            return this;
        }

        /**
         * 设置表单参数，与body参数冲突，只能设置其中一个，优先使用body
         */
        public HttpClient postParams(Map<String, String> postParams) {
            this.postParams.putAll(postParams);
            return this;
        }

        /**
         * 添加表单参数
         */
        public HttpClient addPostParam(String key, String value) {
            this.postParams.put(key, value);
            return this;
        }

        /**
         * 设置请求体body，与post参数冲突，只能设置其中一个
         */
        public HttpClient body(Object body) {
            this.body = body;
            return this;
        }

        /**
         * 设置请求体body并指定json格式发送，与post参数冲突，只能设置其中一个
         */
        public HttpClient jsonBody(Object body) {
            return body(body).addHeader("Content-Type", "application/json;charset=" + charset);
        }

        /**
         * 上传文件流
         *
         * @param formName    表单参数名称
         * @param filename    文件名
         * @param inputStream 文件流
         * @return
         */
        public HttpClient uploadInputStream(String formName, String filename, InputStream inputStream) {
            if (StringUtils.isBlank(formName)) {
                throw new IllegalArgumentException("formName 不能为空");
            }
            if (StringUtils.isBlank(filename)) {
                throw new IllegalArgumentException("filename 不能为空");
            }
            if (inputStream == null) {
                throw new IllegalArgumentException("上传数据流不能为空");
            }
            UploadStream uploadStream = new UploadStream();
            uploadStream.setFormName(formName);
            uploadStream.setFilename(filename);
            uploadStream.setInputStream(inputStream);
            if (uploadStreams == null) {
                uploadStreams = new ArrayList<>();
            }
            uploadStreams.add(uploadStream);
            return this;
        }

        /**
         * 设置请求体body并指定json格式发送，与post参数冲突，只能设置其中一个
         */
        public HttpClient uploadFile(String formName, File file) throws FileNotFoundException {
            if (StringUtils.isBlank(formName)) {
                throw new IllegalArgumentException("formName 不能为空");
            }

            return uploadInputStream(formName, file.getName(), new FileInputStream(file));
        }

        /**
         * 本次请求为下载文件，注意请求的响应对象为 {@link HttpResponseStream}
         */
        public HttpClient download() {
            downloadFileType = 1;
            return this;
        }

        /**
         * 本次请求可能为下载文件，会自动识别，注意请求的响应对象为 {@link HttpResponseStream}
         */
        public HttpClient autoDownload() {
            downloadFileType = 2;
            return this;
        }

        /**
         * 禁用ssl认证
         */
        public HttpClient disableSsl() {
            addInitializer(new HttpsTrustAllAuthInitializer());
            return this;
        }

        /**
         * 获取最终的请求地址
         */
        public String getRequestUrl() {
            return transformUrl(this.url, pathParams, queryParams);
        }

        /**
         * 发起请求
         */
        public HttpResponse request() throws IOException {
            response = RestfulHttpClient.request(this);
            return response;
        }

        /**
         * 发起异步请求
         */
        public Future<HttpResponse> asyncRequest() throws IOException {
            return getPoolExecutor().submit(() -> {
                response = request();
                return response;
            });
        }

        /**
         * 上传文件
         */
        public HttpResponse upload() throws IOException {
            response = RestfulHttpClient.upload(this);
            return response;
        }

        /**
         * 异步上传
         */
        public Future<HttpResponse> asyncUpload() throws IOException {
            return getPoolExecutor().submit(() -> {
                response = upload();
                return response;
            });
        }

        /**
         * 添加请求连接初始化器
         */
        public HttpClient addInitializer(URLConnectionInitializer initializer) {
            if (initializer == null) {
                throw new NullPointerException("不能添加空的连接初始化器");
            }
            if (initializers == null) {
                initializers = new ArrayList<>();
            }
            initializers.add(initializer);
            return this;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public int getReadTimeout() {
            return readTimeout;
        }

        public int getConnectTimeout() {
            return connectTimeout;
        }

        public String getMethod() {
            return method;
        }

        public String getUrl() {
            return url;
        }

        public Map<String, String> getQueryParams() {
            return queryParams;
        }

        public Map<String, String> getPathParams() {
            return pathParams;
        }

        public Map<String, String> getPostParams() {
            return postParams;
        }

        public <T> T getBody() {
            return (T) body;
        }

        public List<UploadStream> getUploadStreams() {
            return uploadStreams;
        }

        public HttpResponse getResponse() {
            return response;
        }
    }

    /**
     * 请求响应结果
     */
    public static class HttpResponse {
        protected int code;
        protected Map<String, List<String>> headers;
        protected String requestUrl;
        protected String content;

        public HttpResponse(int code, Map<String, List<String>> headers, String requestUrl, String content) {
            this.code = code;
            this.headers = headers;
            this.requestUrl = requestUrl;
            this.content = content;
        }

        public <T> T getContent(Class<T> clz) throws IOException {
            if (StringUtils.isNotBlank(getContent())) {
                return new ObjectMapper().readValue(getContent(), clz);
            }
            return null;
        }

        /**
         * 获取响应状态码
         */
        public int getCode() {
            return code;
        }

        /**
         * 获取响应头
         */
        public Map<String, List<String>> getHeaders() {
            return headers;
        }

        /**
         * 获取最后请求地址
         */
        public String getRequestUrl() {
            return requestUrl;
        }

        /**
         * 获取响应内容
         */
        public String getContent() {
            return content;
        }
    }

    /**
     * 请求响应结果为文件流，用完需要手动关闭
     */
    public static class HttpResponseStream extends HttpResponse implements Closeable {
        private String filename;
        private long fileLength;
        private HttpURLConnection connection;
        private byte[] data;
        private int bufferSize = 1024 * 1024;

        public HttpResponseStream(int code, Map<String, List<String>> headers, String requestUrl, HttpURLConnection connection) {
            super(code, headers, requestUrl, null);
            this.connection = connection;
        }

        /**
         * 获取具体数据
         */
        public byte[] getData() throws IOException {
            if (data == null) {
                try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                    saveTo(outputStream);
                    data = outputStream.toByteArray();
                }
            }
            return data;
        }

        /**
         * 获取文件流
         */
        public InputStream getInputStream() throws IOException {
            return connection.getInputStream();
        }

        /**
         * 输出到指定的流
         */
        public void saveTo(OutputStream outputStream) throws IOException {
            try (InputStream inputStream = getInputStream()) {
                byte[] buffer = new byte[bufferSize];
                int size;
                while ((size = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, size);
                }
            } finally {
                outputStream.close();
            }
        }

        /**
         * 保存文件
         *
         * @param file 目录文件
         * @throws IOException
         */
        public void saveTo(File file) throws IOException {
            if (file.isDirectory()) {
                if (StringUtils.isBlank(filename)) {
                    throw new IllegalArgumentException("自动识别文件名失败，请指定文件名");
                }
                file = new File(file, filename);
            }
            saveTo(new FileOutputStream(file));
        }


        /**
         * 保存文件
         *
         * @param path 保存路径，如果是路径是/结束，则认为保存目录
         * @throws IOException
         */
        public void saveTo(String path) throws IOException {
            if (path.endsWith("/") || path.endsWith("\\")) {
                if (StringUtils.isBlank(filename)) {
                    throw new IllegalArgumentException("自动识别文件名失败，请指定文件名");
                }
                saveTo(path, filename);
                return;
            }
            saveTo(new File((path)));
        }

        /**
         * 保存文件
         *
         * @param dir      保存目录
         * @param filename 文件名
         * @throws IOException
         */
        public void saveTo(String dir, String filename) throws IOException {
            saveTo(new File(dir, filename));
        }

        @Override
        public String getContent() {
            if (content == null) {
                try {
                    content = readResponseContent(connection);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return content;
        }

        public String getFilename() {
            return filename;
        }

        public long getFileLength() {
            return fileLength;
        }

        public HttpURLConnection getConnection() {
            return connection;
        }

        public int getBufferSize() {
            return bufferSize;
        }

        public void setBufferSize(int bufferSize) {
            this.bufferSize = bufferSize;
        }

        @Override
        public void close() throws IOException {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    public static class UploadStream {
        private String formName;
        private String filename;
        private InputStream inputStream;

        public String getFormName() {
            return formName;
        }

        public void setFormName(String formName) {
            this.formName = formName;
        }

        public String getFilename() {
            return filename;
        }

        public void setFilename(String filename) {
            this.filename = filename;
        }

        public InputStream getInputStream() {
            return inputStream;
        }

        public void setInputStream(InputStream inputStream) {
            this.inputStream = inputStream;
        }
    }

    /**
     * 发起请求
     */
    private static HttpResponse request(HttpClient client) throws IOException {
        HttpURLConnection con = instance(client);
        if (METHOD_GET.equalsIgnoreCase(client.getMethod())) {
            //GET请求，不用发请求体
            return readResponse(client, con);
        }
        Object requestBody = null;
        if (client.getBody() instanceof byte[] || client.getBody() instanceof InputStream) {
            //输出流数据
            requestBody = client.getBody();
        } else if (isPrimitiveOrWrapClzOrStr(client.getBody())) {
            requestBody = client.getBody().toString();
        } else if (client.getBody() != null) {
            requestBody = new ObjectMapper().writeValueAsString(client.getBody());
            if (!client.getHeaders().containsKey("Content-Type")) {
                //设置请求媒体类型为json提交
                con.setRequestProperty("Content-Type", "application/json;charset=" + charset);
                client.addHeader("Content-Type", "application/json;charset=" + charset);
            }
        } else if (client.getPostParams() != null && !client.getPostParams().isEmpty()) {
            requestBody = toUrlParams(client.getPostParams());
            if (!client.getHeaders().containsKey("Content-Type")) {
                //设置请求媒体类型为表单提交
                con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
                client.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
            }
        }
        if (!client.getHeaders().containsKey("Content-Type")) {
            con.setRequestProperty("Content-Type", "application/octet-stream");
        }

        con.setDoOutput(true);
        con.setDoInput(true);

        writeRequestBody(requestBody, con.getOutputStream());

        return readResponse(client, con);
    }

    /**
     * 输出请求体
     */
    private static void writeRequestBody(Object requestBody, OutputStream out) throws IOException {
        if (requestBody instanceof String) {
            try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, charset))) {
                bw.write((String) requestBody);
                bw.flush();
            }
        } else if (requestBody instanceof byte[]) {
            try (DataOutputStream dOut = new DataOutputStream(out)) {
                dOut.write((byte[]) requestBody);
                dOut.flush();
            }
        } else if (requestBody instanceof InputStream) {
            try (BufferedOutputStream bOut = new BufferedOutputStream(out)) {
                InputStream is = (InputStream) requestBody;
                byte[] buffer = new byte[4096];
                int size;
                while ((size = is.read(buffer)) > 0) {
                    bOut.write(buffer, 0, size);
                }
                bOut.flush();
                is.close();
            }
        }
    }

    /**
     * 上传文件
     */
    private static HttpResponse upload(HttpClient client) throws IOException {
        if (client.getBody() != null) {
            throw new IllegalArgumentException("文件上传不支持body参数");
        }

        HttpURLConnection con = instance(client);
        String boundary = "====RestfulHttpClient---------";
        con.setRequestMethod("POST");
        con.setRequestProperty("Connection", "Keep-Alive");
        con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);

        con.setDoOutput(true);
        con.setDoInput(true);

        DataOutputStream out = new DataOutputStream(con.getOutputStream());
        try {
            if (client.getPostParams() != null && !client.getPostParams().isEmpty()) {
                StringBuilder postParams = new StringBuilder();
                for (Map.Entry<String, String> entry : client.getPostParams().entrySet()) {
                    if (StringUtils.isBlank(entry.getKey())) {
                        continue;
                    }

                    postParams.append("--").append(boundary).append("\r\n");
                    postParams.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append("\"\r\n\r\n");
                    postParams.append(URLEncoder.encode(entry.getValue(), charset));
                    postParams.append("\r\n");
                }

                if (postParams.length() > 0) {
                    out.writeBytes(postParams.toString());
                }
            }

            if (client.getUploadStreams() != null && !client.getUploadStreams().isEmpty()) {
                for (UploadStream uploadStream : client.getUploadStreams()) {
                    out.writeBytes("--" + boundary + "\r\n");
                    out.writeBytes("Content-Disposition: form-data; name=\"" + uploadStream.getFormName() +
                            "\"; filename=\"" + uploadStream.getFilename() + "\"\r\n");
                    out.writeBytes("Content-Type: " + URLConnection.guessContentTypeFromName(uploadStream.getFilename()));
                    out.writeBytes("\r\n\r\n");

                    IOUtils.copy(uploadStream.getInputStream(), out);
                    out.writeBytes("\r\n");
                    out.flush();

                    IOUtils.closeQuietly(uploadStream.getInputStream());
                }
            }

            out.writeBytes("--" + boundary + "--\r\n");
        } finally {
            IOUtils.closeQuietly(out);
        }


        return readResponse(client, con);
    }

    /**
     * 将map参数转成url参数形式：name1=value2&name2=value2...
     */
    public static String toUrlParams(Map<String, String> paras) {
        if (paras != null && !paras.isEmpty()) {
            StringBuilder urlParams = new StringBuilder();
            for (String k : paras.keySet()) {
                urlParams.append(k).append("=").append(paras.get(k)).append("&");
            }
            if (urlParams.length() > 0) {
                return urlParams.substring(0, urlParams.length() - 1);
            }
        }
        return null;
    }

    /**
     * 获取全局默认请求headers设置
     */
    public static Map<String, String> getDefaultHeaders() {
        return defaultHeaders;
    }

    /**
     * 设置默认全局默认请求headers
     */
    public static Map<String, String> setDefaultHeaders(Map<String, String> headers) {
        defaultHeaders.clear();
        defaultHeaders.putAll(headers);
        return getDefaultHeaders();
    }

    /**
     * 添加普通文本类型媒体，支持正则表达式匹配
     */
    public static void addTextContentTypes(Collection<String> textContentTypes) {
        RestfulHttpClient.textContentTypes.addAll(textContentTypes);
    }

    /**
     * 异步请求线程的线程池
     *
     * @param poolExecutor 用来保存异步请求线程的线程池
     * @return
     */
    public static void setPoolExecutor(ExecutorService poolExecutor) {
        if (poolExecutor == null) {
            throw new NullPointerException("poolExecutor 不能为 null");
        }
        if (RestfulHttpClient.poolExecutor != null) {
            RestfulHttpClient.poolExecutor.shutdown();
        }
        RestfulHttpClient.poolExecutor = poolExecutor;
    }

    /**
     * 连接初始化器
     */
    public interface URLConnectionInitializer {
        /**
         * 初始化http请求连接对象
         *
         * @param connection
         * @return
         */
        HttpURLConnection init(HttpURLConnection connection, HttpClient client);
    }

    /**
     * 判断是否是字符串或基本类型或包装类型
     */
    private static boolean isPrimitiveOrWrapClzOrStr(Object o) {
        if (o == null) {
            return false;
        } else if (o instanceof String) {
            //是字符串类型
            return true;
        } else if (o.getClass().isPrimitive()) {
            //是基本类型
            return true;
        } else {
            try {
                //是包装类型
                return ((Class) o.getClass().getField("TYPE").get(null)).isPrimitive();
            } catch (Exception e) {
                return false;
            }
        }
    }

    private static HttpResponse readResponse(HttpClient client, HttpURLConnection con) throws IOException {
        if (client.downloadFileType == 0) {
            return readResponse(con);
        } else if (client.downloadFileType == 1) {
            return readResponseStream(con);
        }

        boolean isStream = StringUtils.isNotBlank(con.getHeaderField("Content-Disposition"));
        if (!isStream) {
            String contentType = con.getHeaderField("Content-Type");
            if (StringUtils.contains(contentType, ";")) {
                contentType = contentType.split(";")[0].trim().toLowerCase();
            }
            isStream = true;
            for (String regex : textContentTypes) {
                if (Pattern.matches(regex, contentType)) {
                    isStream = false;
                    break;
                }
            }
        }
        if (isStream) {
            return readResponseStream(con);
        } else {
            return readResponse(con);
        }
    }

    /**
     * 读取响应结果
     */
    private static HttpResponse readResponse(HttpURLConnection con) throws IOException {
        return new HttpResponse(con.getResponseCode(), con.getHeaderFields(),
                con.getURL().toString(), readResponseContent(con));
    }

    private static String readResponseContent(HttpURLConnection con) throws IOException {
        BufferedReader br = null;
        try {
            try {
                br = new BufferedReader(new InputStreamReader(con.getInputStream(), charset));
            } catch (Exception e) {
                if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    throw e;
                }

                if (con.getErrorStream() != null) {
                    br = new BufferedReader(new InputStreamReader(con.getErrorStream(), charset));
                }
            }
            if (br == null) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            String read;
            while ((read = br.readLine()) != null) {
                sb.append(read);
                sb.append("\n");
            }
            return sb.toString();
        } finally {
            if (br != null) {
                br.close();
            }
            con.disconnect();
        }
    }

    /**
     * 读取响应结果
     */
    private static HttpResponse readResponseStream(HttpURLConnection con) throws IOException {
        final HttpResponseStream httpResponseFile = new HttpResponseStream(con.getResponseCode(), con.getHeaderFields(),
                con.getURL().toString(), con);
        httpResponseFile.fileLength = con.getContentLengthLong();
        String headerFields = con.getHeaderField("Content-Disposition");
        String[] fields = StringUtils.split(headerFields, ";");
        if (fields != null) {
            String filenameFlag = "filename=";
            for (String s : fields) {
                s = s.trim();
                if (s.toLowerCase().startsWith(filenameFlag)) {
                    httpResponseFile.filename = s.substring(filenameFlag.length()).replaceAll("\"", "");
                }
            }
        }
        if (StringUtils.isBlank(httpResponseFile.filename)) {
            String requestUrl = httpResponseFile.getRequestUrl();
            String filename = requestUrl.substring(requestUrl.lastIndexOf("/") + 1);
            if (filename.contains(".")) {
                // 如果有后缀才认为是文件名
                httpResponseFile.filename = filename;
            }
        }

        return httpResponseFile;
    }

    /**
     * 初始化请求连接
     */
    private static HttpURLConnection instance(HttpClient client) throws IOException {
        URL u = new URL(client.getRequestUrl());
        HttpURLConnection con = (HttpURLConnection) u.openConnection();
        con.setReadTimeout(client.getReadTimeout());
        con.setConnectTimeout(client.getConnectTimeout());
        con.setRequestMethod(client.getMethod());
        Map<String, String> headers = client.getHeaders();
        if (headers != null && !headers.isEmpty()) {
            for (String key : headers.keySet()) {
                con.setRequestProperty(key, headers.get(key));
            }
        }

        List<URLConnectionInitializer> initializers = client.initializers;
        if (initializers != null && !initializers.isEmpty()) {
            for (URLConnectionInitializer initializer : initializers) {
                HttpURLConnection init = initializer.init(con, client);
                if (init != null) {
                    con = init;
                }
            }
        }
        return con;
    }

    /**
     * 处理url的参数
     * 如：/user/{id}，将id转成值
     *
     * @param url
     * @param pathParams  地址参数
     * @param queryParams 请求参数
     * @return
     */
    private static String transformUrl(String url, Map<String, String> pathParams, Map<String, String> queryParams) {
        if (pathParams != null && !pathParams.isEmpty()) {
            for (String key : pathParams.keySet()) {
                url = url.replaceAll("\\{" + key + "\\}", pathParams.get(key));
            }
        }
        if (queryParams != null && !queryParams.isEmpty()) {
            if (url.indexOf("?") > 0) {
                url += "&" + toUrlParams(queryParams);
            } else {
                url += "?" + toUrlParams(queryParams);
            }
        }
        return url;
    }

    private static ExecutorService getPoolExecutor() {
        if (poolExecutor == null) {
            poolExecutor = initPoolExecutor();
        }
        return poolExecutor;
    }

    private static ExecutorService initPoolExecutor() {
        int cpuCount = Runtime.getRuntime().availableProcessors();
        return new ThreadPoolExecutor(cpuCount, cpuCount + 10, 1, TimeUnit.MINUTES,
                new ArrayBlockingQueue<Runnable>(1024));
    }

    public static void main(String[] args) throws IOException {
        {
            /* 使用例子 */
            String url = "http://localhost:8081/user/{id}";

            RestfulHttpClient.HttpResponse response = getClient(url)
                    .post()              //设置post请求
                    .addHeader("token", "tokenValue")   //添加header
                    .addPathParam("id", "2")        //设置url路径参数
                    .addQueryParam("test", "value") //设置url请求参数
                    .request();         //发起请求

            System.out.println(response.getCode());     //响应状态码
            System.out.println(response.getRequestUrl());//最终发起请求的地址
            if (response.getCode() == 200) {
                //请求成功
                System.out.println(response.getContent());  //响应内容
            }
        }

        {
            /* 关闭https协议证书认证 */
            // HttpsTrustAllAuthInitializer 类代码下载：https://gitee.com/lnkToKing/open-share-code/blob/master/src/utils/http/HttpsTrustAllAuthInitializer.java
//            RestfulHttpClient.addInitializer(HttpsTrustAllAuthInitializer.getInstance());
            String url = "https://b2b.10086.cn/b2b/main/viewNoticeContent.html";

            RestfulHttpClient.HttpResponse response = RestfulHttpClient.getClient(url).request();

            System.out.println(response.getCode());
            System.out.println(response.getContent());
        }

        {
            /* https协议请求支持，自动加载证书 */
            // HttpsAutoAuthInitializer 类代码下载：https://gitee.com/lnkToKing/open-share-code/blob/master/src/utils/http/HttpsAutoAuthInitializer.java
//            RestfulHttpClient.addInitializer(HttpsAutoAuthInitializer.getInstance());
            // 设置证书保存路径，默认程序启动目录
            HttpsAutoAuthInitializer.getInstance().setCertificateDir("D://");
            String url = "https://b2b.10086.cn/b2b/main/viewNoticeContent.html";

            RestfulHttpClient.HttpResponse response = RestfulHttpClient.getClient(url).request();

            System.out.println(response.getCode());
            System.out.println(response.getContent());
        }

        {
            /* https协议请求支持，指定证书 */
            String url = "https://b2b.10086.cn/b2b/main/viewNoticeContent.html";
            final HttpClient client = RestfulHttpClient.getClient(url);

            // HttpsCertificateAuthInitializer 类代码下载：https://gitee.com/lnkToKing/open-share-code/blob/master/src/utils/http/HttpsCertificateAuthInitializer.java
//            final HttpsCertificateAuthInitializer cert = new HttpsCertificateAuthInitializer("证书路径");
//            cert.setPassword("123456");
//            client.addInitializer(cert);
            RestfulHttpClient.HttpResponse response = client.request();

            System.out.println(response.getCode());
            System.out.println(response.getContent());
        }

        {
            /* 异步请求 */
            try {
                Future<HttpResponse> future = getClient("https://www.baidu.com")
                        .get()
                        .asyncRequest();
                System.out.println("do something else");
                HttpResponse response = future.get();
                System.out.println(response.getContent());
            } catch (IOException | InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        {
            /* 下载文件 */
            // 指定响应结果为文件流之后，返回的响应对象为 HttpResponseStream
            HttpResponseStream request = (HttpResponseStream) getClient("http://down10.zol.com.cn/office/gongzibiao.rar")
                    // 指定响应结果为文件流
                    .download()
                    // 发起请求
                    .request();
            if (request.getCode() == 200) {
                // 文件名，自动识别，可能为空
                System.out.println(request.getFilename());
                // 文件大小，如果为-1或0是服务器端没返回文件大小
                System.out.println(request.getFileLength());

                // 保存文件，下载几种方式只能使用一种，保存之后不能再使用这些方法
                // 保存为指定文件
                request.saveTo("D:/test.rar");
                // 保存到指定目录，文件名为自动识别的文件名
                request.saveTo("D:/");
                // 保存到指定目录下，并指定文件名
                request.saveTo("D:/download", "test.rar");
                // 输出到指定输出流
                request.saveTo(new ByteArrayOutputStream());
                // 直接获取响应数据
                byte[] data = request.getData();
            } else {
                // 下载失败，输出结果
                System.out.println(request.getContent());
            }
        }

        {
            /* 自动识别下载文件 */
            HttpResponse response = getClient("https://www.baidu.com/img/flexible/logo/pc/result.png")
                    // 自动识别是文件下载还是普通请求
                    .autoDownload()
                    .request();

            System.out.println(response.getCode());     //响应状态码
            if (response instanceof HttpResponseStream) {
                // 返回的是文件流
                HttpResponseStream responseStream = (HttpResponseStream) response;
                responseStream.saveTo("D:/baidu.png");
                responseStream.close();
                System.out.println("download success");
            } else {
                // 普通请求或失败结果
                System.out.println(response.getContent());  //响应内容
            }
        }


//        {
//            /* 测试多线程 */
//            final ExecutorService executorService = Executors.newFixedThreadPool(3);
//            List<Future> list = new ArrayList<>();
//            for (int i = 0; i < 100; i++) {
//                final Future<?> submit = executorService.submit(() -> {
//                    final long l = System.currentTimeMillis();
//                    try {
//                        HttpResponse request = getClient("https://www.baidu.com")
//                                .autoDownload()
//                                .request();
//                        System.out.println(System.currentTimeMillis() - l);
//                        System.out.println(request.getContent());
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
//                });
//                list.add(submit);
//            }
//
//            for (Future thread : list) {
//                try {
//                    thread.get();
//                } catch (ExecutionException | InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        }

        {
            /* 文件上传 */
            final HttpResponse response = RestfulHttpClient.getClient("http://localhost:8080/upload")
                    .addPostParam("test", "haha")
//                .uploadFile("file", new File("D:\\RestfulHttpClient.java"))
                    .uploadInputStream("file", "test.txt", new ByteArrayInputStream("test upload".getBytes()))
                    .upload();
            System.out.println(response.getCode());
            System.out.println(response.getContent());
        }
    }
}
